#
# Copyright (c) 2011, EPFL (Ecole Politechnique Federale de Lausanne)
# All rights reserved.
#
# Created by Marco Canini, Daniele Venzano, Dejan Kostic, Jennifer Rexford
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
#   -  Redistributions of source code must retain the above copyright notice,
#      this list of conditions and the following disclaimer.
#   -  Redistributions in binary form must reproduce the above copyright notice,
#      this list of conditions and the following disclaimer in the documentation
#      and/or other materials provided with the distribution.
#   -  Neither the names of the contributors, nor their associated universities or
#      organizations may be used to endorse or promote products derived from this
#      software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
# OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT
# SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#

import cPickle

from lib.of_context import Context
from lib.action import Action

import logging
log = logging.getLogger("nice.mc.model")
from stats import getStats
stats = getStats()

class Model:
	def __init__(self, config):
		# entities in the topology
		self.config = config
		self.switches = []
		self.switches_idx = {}
		self.controller = None
		self.clients = []
		self.nodes = {} # Keep track of nodes by their name because references are not stable
		self.node_hashes = {}
		self.fault_injection_count = 0
		self.packet_counters = {} # used to generate packet IDs
		self.actionList = []
		self.useDpor = False;

		self.of_context = Context(self)

	def initTopology(self, topo):
		raise NotImplementedError

	def setFaultInjectionCount(self, num_faults):
		self.fault_injection_count = num_faults
		for s in self.switches:
			s.setFaultInjectionCount(num_faults)

	def getNodes(self):
		objs = self.clients + self.switches
		if not self.controller is None:
			objs.append(self.controller)
		return objs

	def start(self, model_checker):
		self.actionList = []
		self.useDpor = model_checker.useDpor;
		for c in self.getNodes():
			self.packet_counters[c.name] = 0
			self.nodes[c.name] = c
			c.start(model_checker)
		for n in self.nodes:
			self.node_hashes[n] = hash(self.nodes[n])

	def genPacketID(self, sender_name):
		pktid = sender_name + str(self.packet_counters[sender_name])
		self.packet_counters[sender_name] += 1
		return pktid

	def printEnabledActions(self):
		ena = self.getAllEnabledActions()
		log.debug("Enabled actions: %s" % ena)

	def getAllEnabledActions(self):
		actions = []
		for c in self.getNodes():
			actions += c.enabled_actions
		return actions

	def executeAction(self, action, depth):
		log.debug("%d -- Executing action: %s" % (depth, str(action)))
		node = self.nodes[action.node_name]
		action2 = action;	
		if hasattr(node, "discover_packets") and action.target == "send_packet":
			action2 = Action(action.node_name, action.target, [])
		if action2 not in self.actionList:
			self.actionList.append(action2)
		node.runAction(action)

	def checkHashes(self):
		for nh in self.node_hashes:
			aux = hash(self.nodes[nh])
			if self.node_hashes[nh] != aux:
				log.debug("Node %s changed state" % nh)
				self.node_hashes[nh] = aux

	def __hash__(self):
		raise NotImplementedError

	def serializedState(self):
		if self.useDpor:
			self.actionList.sort()
			return cPickle.dumps(self.clients + self.switches + [self.controller] + self.actionList, -1)
		else:
			return cPickle.dumps(self.clients + self.switches + [self.controller], -1)

	def nodesState(self):
		serialized_state = {}
		for c in self.getNodes():
			serialized_state[c.name] = cPickle.dumps(c, -1)
		return serialized_state

	def __repr__(self):
		s = cPickle.dumps(self.clients, -1)
		s = s + "-----"
		s = s + cPickle.dumps(self.switches, -1)
		s = s + "-----"
		s = s + cPickle.dumps(self.controller, -1)
		return s

	def getControllerAppState(self):
		return self.controller.getControllerAppState()

	def getClientMacAddresses(self):
		l = []
		for c in self.clients:
			l.append(c.mymac.data)
		return l

